// Copyright (C) 2022 Linlexiao
#include "problem.h"

#include <QDebug>
#include <random>
ProblemGenerator::ProblemGenerator() = default;

Problem ProblemGenerator::generate() {
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < ((i == 3) ? 8 : 10); j++) {
            paiLeft[i * 9 + j] = 4;
        }
    }
    akaDoraLeft[MAN] = 1;
    akaDoraLeft[PIN] = 1;
    akaDoraLeft[SUO] = 1;
    doraCnt = 1;
    auto rnd = QRandomGenerator::global();
    field = Wind(rnd->bounded(2));
    seat = Wind(rnd->bounded(4));

    HandFlag agariWay;
    QVector<HandFlag> yakus = {};
    if (_testChance(tsumoChance)) {
        agariWay = TSUMO;
    } else {
        agariWay = RON;
    }
    QVector<Pai> hand;
    QVector<Pai> dora;
    QVector<Pai> ura;
    QVector<Block> furu;

    if (_testChance(kokushiChance)) {
        QVector<Pai> kokushiHand = Pai::fromStrings("19s19p19m1234567z");
        std::shuffle(kokushiHand.begin(), kokushiHand.end(),
                     std::mt19937(std::random_device()()));
        kokushiHand.push_back(kokushiHand.back());
        hand = kokushiHand;
    } else if (_testChance(chituiChance)) {
        for (int tuiCnt = 0; tuiCnt < 7;) {
            auto tp = PaiType(rnd->bounded(4));
            int num;
            if (tp == ZI)
                num = rnd->bounded(1, 8);
            else
                num = rnd->bounded(1, 10);

            if (_canGetPai(tp, num,
                           3)) {  //为了确保没有龙七对 只有剩三张以上才生成对子
                tuiCnt++;
                hand.push_back(_getPai(tp, num));
                hand.push_back(_getPai(tp, num));
            }
        }
        if (_testChance(riichiChance)) yakus.push_back(RIICHI);
    } else {  //普通形生成
        int callCount = rnd->bounded(-4, 5);
        if (callCount < 0) callCount = 0;
        if (callCount) {  //非门前清
            bool wantTanyao = _testChance(0.5);

            bool generatedYakuhai = false;
            for (int i = 0; i < (4 - callCount); i++) {
                QVector<Pai> p;
                if (!wantTanyao) {
                    if (i == 0 && _testChance(0.5)) {
                        p = _generateYakuhaiBlock();
                        generatedYakuhai = true;
                    } else
                        p = _generateRandomBlock();
                } else {
                    p = _generateTanyaoBlock();
                }
                for (auto x : p) hand.push_back(x);
            }
            for (int i = 0; i < callCount; i++) {
                if (!wantTanyao) {
                    if (i == 0) {
                        if ((!generatedYakuhai) ||
                            (generatedYakuhai && _testChance(0.1)))
                            furu.push_back(_generateYakuhaiFuru());
                        else
                            furu.push_back(_generateRandomFuru());
                    } else
                        furu.push_back(_generateRandomFuru());
                } else {
                    furu.push_back(_generateTanyaoFuru());
                }
            }
            QVector<Pai> p;
            if (!wantTanyao)
                p = _generateRandomPair();
            else
                p = _generateTanyaoPair();
            for (auto x : p) hand.push_back(x);
        } else {
            bool wantTanyao = _testChance(0.2);
            bool wantYakuhai = false;
            if (!wantTanyao) wantYakuhai = _testChance(0.2);
            for (int i = 0; i < 4; i++) {
                QVector<Pai> p;
                if (wantTanyao)
                    p = _generateTanyaoBlock();
                else if (wantYakuhai && i == 0)
                    p = _generateYakuhaiBlock();
                else
                    p = _generateRandomBlock();
                for (auto x : p) hand.push_back(x);
            }
            if ((!wantTanyao && !wantYakuhai) || _testChance(riichiChance))
                yakus.push_back(RIICHI);
            QVector<Pai> p;
            if (wantTanyao)
                p = _generateTanyaoPair();
            else
                p = _generateRandomPair();
            for (auto x : p) hand.push_back(x);
        }
    }
    std::sort(hand.begin(), hand.end());

    int agariIndex = rnd->bounded(hand.length());

    auto agariPai = hand[agariIndex];
    hand.remove(agariIndex);
    //    doraCnt = 5;
    //生成dora ura
    for (int i = 0; i < doraCnt; i++) {
        dora.push_back(_getRandomPai());
        if (yakus.contains(RIICHI)) ura.push_back(_getRandomPai());
    }

    if (_testChance(haiteiHouteiChance)) {
        if (agariWay == TSUMO) {
            yakus.push_back(HAITEI_RAOYUE);
        } else {
            yakus.push_back(HOUTEI_RAOYUI);
        }
    }

    if (doraCnt > 1 && _testChance(rinnshannKaihouChance)) {
        yakus.push_back(RINNSHANN_KAIHOU);
    }

    if (yakus.contains(RIICHI)) {
        if (_testChance(doubleRiichiChance)) {
            yakus.removeOne(RIICHI);
            yakus.push_back(DOUBLE_RIICHI);
        }
        if (_testChance(ippastsuChance)) {
            yakus.push_back(IPPATSU);
        }
    }

    //    QString msg = "副露:";
    //    for(auto x:furu[0].getPai()){
    //        msg+=x.toString();
    //    }
    //    qDebug()<<msg;
    //    qDebug()<<213;
    //    field = EAST;
    //    seat = EAST;
    //    agariWay = TSUMO;
    //    agariPai = Pai("1z");
    //    hand = Pai::fromStrings("123p123m456s1z");
    //    furu = {Block(SEQUENCE, SUO, 1, 1, 0)};
    //    furu = {
    //        Block::fromPais(Pai::fromStrings("1111z"), 0),
    //        Block::fromPais(Pai::fromStrings("2222z"), 0),
    //        Block::fromPais(Pai::fromStrings("3333z"), 0),
    //        Block::fromPais(Pai::fromStrings("4444z"), 0),
    //    };
    //    dora = Pai::fromStrings("1111m1s");
    //    ura = Pai::fromStrings("44599m");
    std::sort(hand.begin(), hand.end());
    State s(field, seat, yakus, agariWay, hand, furu, dora, ura, agariPai);
    Problem rt;
    rt.hand = hand;
    rt.agariPai = agariPai;
    rt.furu = furu;
    rt.dora = dora;
    rt.ura = ura;
    rt.flag = s.flag;
    rt.ans = c.calculate(s);
    //    qDebug()<<213;
    return rt;
}

void Problem::calculateResult() {
    State s(EMPTY_WIND, EMPTY_WIND, {}, NOMI, hand, furu, dora, ura, agariPai);
    s.flag = flag;
    Calculator c;
    ans = c.calculate(s);
}

QVector<Pai> ProblemGenerator::_generateTanyaoBlock() {
    auto rnd = QRandomGenerator::global();
    bool success = false;
    QVector<Pai> rt;
    do {
        auto tp = PaiType(rnd->bounded(3));
        if (_testChance(tripletChance)) {
            int num = rnd->bounded(2, 9);
            if (_canGetPai(tp, num, 3)) {
                success = true;
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num));
            }
        } else {
            int num = rnd->bounded(2, 7);
            if (_canGetPai(tp, num) && _canGetPai(tp, num + 1) &&
                _canGetPai(tp, num + 2)) {
                success = true;
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num + 1));
                rt.push_back(_getPai(tp, num + 2));
            }
        }
    } while (!success);  //易证明不会死循环
    return rt;
}

QVector<Pai> ProblemGenerator::_generateYakuhaiBlock() {
    QVector<int> yakuHais = {field + 1, seat + 1, 5, 6, 7};
    std::shuffle(yakuHais.begin(), yakuHais.end(),
                 std::mt19937(std::random_device()()));
    for (int num : yakuHais) {
        if (_canGetPai(ZI, num, 3)) {
            return {_getPai(ZI, num), _getPai(ZI, num), _getPai(ZI, num)};
        }
    }
    return _generateRandomBlock();
}

QVector<Pai> ProblemGenerator::_generateRandomBlock() {
    auto rnd = QRandomGenerator::global();
    bool success = false;
    QVector<Pai> rt;
    do {
        auto tp = PaiType(rnd->bounded(4));
        if (_testChance(tripletChance) || tp == ZI) {
            int num;
            if (tp == ZI)
                num = rnd->bounded(1, 8);
            else
                num = rnd->bounded(1, 10);
            if (_canGetPai(tp, num, 3)) {
                success = true;
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num));
            }
        } else {
            int num = rnd->bounded(1, 8);
            if (_canGetPai(tp, num) && _canGetPai(tp, num + 1) &&
                _canGetPai(tp, num + 2)) {
                success = true;
                rt.push_back(_getPai(tp, num));
                rt.push_back(_getPai(tp, num + 1));
                rt.push_back(_getPai(tp, num + 2));
            }
        }
    } while (!success);  //易证明不会死循环
    return rt;
}

QVector<Pai> ProblemGenerator::_generateTanyaoPair() {
    auto rnd = QRandomGenerator::global();

    bool success = false;
    QVector<Pai> rt;
    do {
        auto tp = PaiType(rnd->bounded(3));
        int num = rnd->bounded(2, 9);
        if (_canGetPai(tp, num, 2)) {
            success = true;
            rt.push_back(_getPai(tp, num));
            rt.push_back(_getPai(tp, num));
        }
    } while (!success);
    return rt;
}

QVector<Pai> ProblemGenerator::_generateYakuhaiPair() {
    QVector<int> yakuHais = {field + 1, seat + 1, 5, 6, 7};
    std::shuffle(yakuHais.begin(), yakuHais.end(),
                 std::mt19937(std::random_device()()));
    for (int num : yakuHais) {
        if (_canGetPai(ZI, num, 2)) {
            return {_getPai(ZI, num), _getPai(ZI, num)};
        }
    }
    return _generateRandomPair();
}

QVector<Pai> ProblemGenerator::_generateRandomPair() {
    auto rnd = QRandomGenerator::global();

    bool success = false;
    QVector<Pai> rt;
    do {
        auto tp = PaiType(rnd->bounded(4));
        int num;
        if (tp == ZI)
            num = rnd->bounded(1, 8);
        else
            num = rnd->bounded(1, 10);
        if (_canGetPai(tp, num, 2)) {
            success = true;
            rt.push_back(_getPai(tp, num));
            rt.push_back(_getPai(tp, num));
        }
    } while (!success);
    return rt;
}

Block ProblemGenerator::_generateRandomFuru() {
    auto rnd = QRandomGenerator::global();
    while (true) {
        auto tp = PaiType(rnd->bounded(4));
        if (_testChance(tripletChance) || tp == ZI) {
            int num;
            if (tp == ZI)
                num = rnd->bounded(1, 8);
            else
                num = rnd->bounded(1, 10);
            if (_testChance(quadChance)) {
                bool isOpen = true;
                if (_testChance(closeQuadChance)) {
                    isOpen = false;
                }
                if (_canGetPai(tp, num, 4)) {
                    QVector<Pai> pais;
                    for (int i = 0; i < 4; i++)
                        pais.push_back(_getPai(tp, num));
                    doraCnt++;
                    return Block::fromPais(pais, isOpen);
                }
            } else {
                if (_canGetPai(tp, num, 3)) {
                    QVector<Pai> pais;
                    for (int i = 0; i < 3; i++)
                        pais.push_back(_getPai(tp, num));
                    return Block::fromPais(pais, true);
                }
            }
        } else {
            int num = rnd->bounded(1, 8);
            if (_canGetPai(tp, num) && _canGetPai(tp, num + 1) &&
                _canGetPai(tp, num + 2)) {
                QVector<Pai> pais;
                for (int i = 0; i < 3; i++)
                    pais.push_back(_getPai(tp, num + i));
                return Block::fromPais(pais, true);
            }
        }
    }
}

Block ProblemGenerator::_generateTanyaoFuru() {
    auto rnd = QRandomGenerator::global();
    while (true) {
        auto tp = PaiType(rnd->bounded(3));
        if (_testChance(tripletChance)) {
            int num = rnd->bounded(2, 9);
            if (_testChance(quadChance)) {
                bool isOpen = true;
                if (_testChance(closeQuadChance)) {
                    isOpen = false;
                }
                if (_canGetPai(tp, num, 4)) {
                    QVector<Pai> pais;
                    for (int i = 0; i < 4; i++)
                        pais.push_back(_getPai(tp, num));
                    doraCnt++;
                    return Block::fromPais(pais, isOpen);
                }
            } else {
                if (_canGetPai(tp, num, 3)) {
                    QVector<Pai> pais;
                    for (int i = 0; i < 3; i++)
                        pais.push_back(_getPai(tp, num));
                    return Block::fromPais(pais, true);
                }
            }
        } else {
            int num = rnd->bounded(2, 7);
            if (_canGetPai(tp, num) && _canGetPai(tp, num + 1) &&
                _canGetPai(tp, num + 2)) {
                QVector<Pai> pais;
                for (int i = 0; i < 3; i++)
                    pais.push_back(_getPai(tp, num + i));
                return Block::fromPais(pais, true);
            }
        }
    }
}

Block ProblemGenerator::_generateYakuhaiFuru() {
    QVector<int> yakuHais = {field + 1, seat + 1, 5, 6, 7};
    std::shuffle(yakuHais.begin(), yakuHais.end(),
                 std::mt19937(std::random_device()()));
    //一定是刻子
    for (auto x : yakuHais) {
        if (_testChance(quadChance)) {
            bool isOpen = true;
            if (_testChance(closeQuadChance)) {
                isOpen = false;
            }
            if (_canGetPai(ZI, x, 4)) {
                QVector<Pai> pais;
                for (int i = 0; i < 4; i++) pais.push_back(_getPai(ZI, x));
                doraCnt++;
                return Block::fromPais(pais, isOpen);
            }
        } else {
            if (_canGetPai(ZI, x, 3)) {
                QVector<Pai> pais;
                for (int i = 0; i < 3; i++) pais.push_back(_getPai(ZI, x));
                return Block::fromPais(pais, true);
            }
        }
    }
    return _generateRandomFuru();
}

Pai ProblemGenerator::_getRandomPai() {
    while (true) {
        auto rnd = QRandomGenerator::global();
        auto tp = PaiType(rnd->bounded(4));
        int num;
        if (tp == ZI) {
            num = rnd->bounded(1, 8);
        } else {
            num = rnd->bounded(1, 10);
        }
        if (_canGetPai(tp, num)) return _getPai(tp, num);
    }
}

bool ProblemGenerator::_canGetPai(PaiType tp, int n, int cnt) {
    //    qDebug()<<"Left"<<paiLeft[tp*9 + n]<<"pai"<<n<<"Want"<<cnt;
    return paiLeft[tp * 9 + n] >= cnt;
}

Pai ProblemGenerator::_getPai(PaiType tp, int n) {
    assert(paiLeft[tp * 9 + n] > 0);
    if (n == 5 && akaDoraLeft[tp] >= 1 && tp != ZI) {
        if (_testChance(akaDoraChance) ||
            akaDoraLeft[tp] >= paiLeft[tp * 9 + n]) {
            akaDoraLeft[tp]--;
            paiLeft[tp * 9 + n]--;
            return {tp, n, true};
        }
    }
    paiLeft[tp * 9 + n]--;
    return {tp, n, false};
}

bool ProblemGenerator::_testChance(double chance) {
    return QRandomGenerator::global()->generateDouble() < chance;
}

Problem::Problem(const QString& s, bool& ok) {
    auto ls = s.split(Problem::sep1);
    if (ls.length() != 6) {
        ok = false;
        return;
    }
    for (int i = 0; i < 6; i++) {
        switch (i) {
            case 0:
                hand = Pai::fromStrings(ls[i]);
                break;
            case 1:
                agariPai = Pai(ls[i]);
                break;
            case 2:
                if (ls[i].length()) {
                    auto lss = ls[i].split(Problem::sep2);
                    for (auto str : lss) {
                        //注：这里最后一位是表示是否破坏门清的副露，在Pai::fromStrings会恰好忽略这一位
                        furu.push_back(Block::fromPais(
                            Pai::fromStrings(str),
                            str[str.length() - 1].digitValue()));
                    }
                }
                break;
            case 3:
                dora = Pai::fromStrings(ls[i]);
                break;
            case 4:
                ura = Pai::fromStrings(ls[i]);
                break;
            case 5:
                flag = HandFlags(ls[i].toUInt());
                break;
        }
    }
    ok = true;
}

QString Problem::toString() {
    QString rt;
    for (auto p : hand) {
        rt += p.toString();
    }
    rt += Problem::sep1;
    rt += agariPai.toString();
    rt += Problem::sep1;
    for (int i = 0; i < furu.length(); i++) {
        if (i != 0) rt += Problem::sep2;
        for (auto p : furu[i].getPai()) {
            rt += p.toString();
        }
        rt += QString::number(furu[i].isOpen);
    }
    rt += Problem::sep1;
    for (auto p : dora) {
        rt += p.toString();
    }
    rt += Problem::sep1;
    for (auto p : ura) {
        rt += p.toString();
    }
    rt += Problem::sep1;
    rt += QString::number((unsigned)flag);
    return rt;
}
